/*
  S.M.A.C.K - An operating system kernel
  Copyright (C) 2010,2011 Mattias Holm and Kristian Rietveld
  For licensing and a full list of authors of the kernel, see the files
  COPYING and AUTHORS.
*/

#ifndef KERNEL__VFS_VFS_H
#define KERNEL__VFS_VFS_H

#include <stdint.h>
#include <stddef.h>
#include <device.h>

#define VFS_FILENAME_MAX 256 //!< Maximum lenght of a filename (incl '\0')
#define VFS_PATH_MAX 1024 //!< Maximum path length (incl '\0')

typedef uint64_t vnode_id_t;
typedef struct vfs_vnode_t vfs_vnode_t;
typedef struct vfs_file_t vfs_file_t;
typedef struct vfs_filesys_t vfs_filesys_t;

// VFS OPS
typedef int (*vfs_mount_fn_t)(vfs_filesys_t*,vfs_vnode_t*,dev_t*,dev_minor_t);
typedef int (*vfs_umount_fn_t)(vfs_vnode_t*);
typedef vfs_vnode_t* (*vfs_root_fn_t)(vfs_vnode_t*);

// VNODE OPS
typedef size_t (*vfs_read_fn_t)(vfs_file_t*, void *, size_t );
typedef size_t (*vfs_write_fn_t)(vfs_file_t*, const void *, size_t);
typedef int (*vfs_seek_fn_t)(vfs_file_t*, off_t, int);
typedef int (*vfs_open_fn_t)(vfs_vnode_t*);
typedef int (*vfs_close_fn_t)(vfs_file_t*);
typedef int (*vfs_create_fn_t)(vfs_vnode_t*);

typedef int (*vfs_ioctl_fn_t)(vfs_file_t*,uint32_t, uint32_t);
typedef int (*vfs_mmap_fn_t)(vfs_vnode_t*);
typedef int (*vfs_fsync_fn_t)(vfs_vnode_t*);

typedef int (*vfs_mkdir_fn_t)(vfs_vnode_t*, const char *);
typedef int (*vfs_readdir_fn_t)(vfs_vnode_t*, size_t *, vfs_vnode_t***);
typedef int (*vfs_rmdir_fn_t)(vfs_vnode_t*);

struct vfs_filesys_t {
  const char *fsname; //!< Name of fs e.g. "FAT32"
  vfs_mount_fn_t mount; //!< Called when mounting
  vfs_umount_fn_t umount; //!< Called when unmounting
  vfs_root_fn_t root; //!< Get root vnode
};

typedef enum {
  VFS_DEVICE, //!< VFS node is a device
  VFS_MOUNT,  //!< VFS node is a mountpoint
  VFS_DATA,   //!< VFS node is a file
  VFS_DIR,    //!< VFS node is a directory
} vfs_node_kind_t;


typedef struct {
  vfs_filesys_t *fs;
  vfs_vnode_t *root;
  dev_t *dev;
  dev_minor_t minor;
  void *fs_data;
} vfs_mount_t;


/*!
  Virtual file operations, each vfs node has a pointer to a structure like this
 */
typedef struct {
  vfs_create_fn_t create;
  vfs_read_fn_t read;
  vfs_write_fn_t write;
  vfs_seek_fn_t seek;
  vfs_open_fn_t open;
  vfs_close_fn_t close;
  vfs_ioctl_fn_t ioctl;
  vfs_fsync_fn_t fsync;
  vfs_mmap_fn_t mmap;
  vfs_mkdir_fn_t mkdir; // Only implement in directories
  vfs_rmdir_fn_t rmdir; // Only implement in directories
  vfs_readdir_fn_t readdir; // Only implement in directories
} vfs_fileops_t;

/*!
  Virtual file system node

  A vfs node represent an entry in the virtual file system. Such an entry
  may be of different kinds which is indicated by the type member. This
  type slot is used to select between the union members. Allowing the vfs
  node to represent either a file, a directory, a mountpoint or a device.

  The vnodes have a reserved area of 32 bytes (256 bit) that can be used
  by the file system for storing per file data (e.g. location info) without
  having to allocate any additonal data in kernel space.
 */
struct vfs_vnode_t {
  struct vfs_vnode_t *parent; //!< Parent node if applicable
  char name[64]; //!< File name
  vfs_filesys_t *fs; //!< File system of this node

  vnode_id_t vnode_no; //!< Vnode number on device
  size_t size; //!< Size of node in bytes

  vfs_fileops_t *fops; //!< File operation structure

  vfs_node_kind_t type;
  union {
    vfs_mount_t mount; // Mounted filesystem
    struct {
      dev_t *dev; // Device driver
      dev_minor_t minor; // Minor mode
    } dev; // Special device node
    struct {
      size_t files_in_dir; //!< Number of files in directory
      vfs_vnode_t **files; //!< Array of vnode pointers, one per file in dir
    } dir;
  } special;
  uint32_t fs_data[8]; //!< For use by the file system
};


/*!
  File descriptor

  A file descriptor is used to keep track of a file. It contains info about
  which vnode the file is associated with and the offset of the file's read/
  write pointer.
 */
struct vfs_file_t {
  vfs_vnode_t *vnode; //!< VFS node for the file
  off_t off;          //!< Read / write pointer
};

/*! Initialises the vfs subsystem

  Vfs_init must be called before any other function with the vfs prefix.
 */
void vfs_init(void);

/*! Mounts the root file system

  Mount parameters (e.g. which device to use and so on), are read from the
  boot parameters.
 */
void vfs_mount_root(void);

//! Register filesystem with VFS subsystem
void vfs_register(vfs_filesys_t *fs);

//! Query for filesystem with given name. Returns NULL if not found.
vfs_filesys_t* vfs_getfilesys(const char *fsname);

//! Allocates a new empty unconnected vnode
vfs_vnode_t* vfs_mknode(void);

/*! Releases a vnode, if the vnode is a directory, or mount point. The
  children must be released first, otherwise the system will leak memory.
*/
void vfs_releasenode(vfs_vnode_t *vnode);

/*!
  Allocates an array for storing vnode pointers. Typical use is for allocating
  directories. E.g
  \code
  vfs_vnode_t **dir_entries = vfs_getdirp(10);
  for (i = 0 ; i < 10 ; i++)
    dir_entries[i] = vfs_mknode();
  \endcode

  \param files Number of files that should fit in the directory node.
*/
vfs_vnode_t** vfs_getdirp(size_t files);
/*!
  Releases a directory node.
  \param vnode The directory node to release.
  \pre Vnode must be a directory.
  \pre The directory contents must be released.
*/
void vfs_releasedirp(vfs_vnode_t *vnode);

//! Adds node to directory node parent
void vfs_dirp_addnode(vfs_vnode_t *parent, vfs_vnode_t *node);

//! Removes node from directory node parent
void vfs_dirp_removenode(vfs_vnode_t *parent, vfs_vnode_t *node);


//! Sets the global vfs root node
void vfs_setroot(vfs_vnode_t *new_root);

/*!
  Mounts filesystem at vnode
  \param mountpoint Vnode where to place the mounted file system root.
  \param fs File system pointer.
  \param dev The device on which the file system resides
  \param minor Minor number of the device on which the fs resides
*/
void vfs_mount(vfs_vnode_t *mountpoint, vfs_filesys_t *fs, dev_t*dev, dev_minor_t minor);

//! Create a special device node
//! Should this be handled by a special FS?
vfs_vnode_t* vfs_mkdev(vfs_vnode_t *parent, const char *name, dev_t *dev, dev_minor_t minor);

//! Create directory with name under parent
vfs_vnode_t* vfs_mkdir(vfs_vnode_t *parent, const char *name);

//! Remove directory with node
int vfs_rmdir(vfs_vnode_t *node);

//! Create file with name under parent dir
vfs_vnode_t* vfs_mkfile(vfs_vnode_t *parent, const char *name);

/*! Opens a file
  \param vnode The virtual file system node representing the file
  \result Returns a vfs_file_t pointer that can be added to the process' file
          list.
 */
vfs_file_t *vfs_open(vfs_vnode_t *vnode);
//! Closes a file
void vfs_close(vfs_file_t *file);

/*! Duplicate a file descriptor */
vfs_file_t *vfs_dup(vfs_file_t *file);

//! Read data from vnode
vfs_file_t* vfs_open(vfs_vnode_t *vnode);

//! Read data from vnode
size_t vfs_read(vfs_file_t *file, void *buff, size_t len);

//! Write data to vnode
size_t vfs_write(vfs_file_t *file, const void *buff, size_t len);

/*! Adjust the read / write pointer of the file
  \param file The file descriptor
  \param offset Offset to adjust read / write pointer with.
  \param whence How to adjust the rw pointer, for further info see posix lseek
         documentation.
 */
off_t vfs_seek(vfs_file_t *file, off_t offset, int whence);

int vfs_ioctl(vfs_file_t *file, uint32_t request, uint32_t param);

//! Open and close directories
vfs_file_t *vfs_opendir(vfs_vnode_t *vnode);
void vfs_readdir(vfs_file_t *file);
void vfs_closedir(vfs_file_t *file);

/*! Find vnode from path

  Vfs_getnode takes the path which is a string of style "/foo/bar", where / is
  the directory separator, parses the string and searches for a corresponding
  vnode. If the vnode is found after traversing the vfs tree, the requested
  vnode is returned.

  \param path File path expression (must describe full path from the root)
  \result If the file is found the vnode representing the file in the path
          expression. If not found, NULL.
*/
vfs_vnode_t* vfs_getnode(const char *path);

void vfs_getpath(vfs_vnode_t *vnode, char *buffer, size_t size);


#endif /* !KERNEL__VFS_VFS_H */
